/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.velocityme.session;

import com.velocityme.entity.ChangeDelta;
import com.velocityme.entity.ContactDetail;
import com.velocityme.entity.ContactDetailType;
import com.velocityme.entity.Contactable;
import com.velocityme.entity.Department;
import com.velocityme.entity.Node;
import com.velocityme.entity.Organisation;
import com.velocityme.entity.Person;
import com.velocityme.enums.ChangeDeltaItemType;
import com.velocityme.enums.Permission;
import com.velocityme.utility.DirectoryDTO;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;

/**
 *
 * @author rcrida
 */
@Stateless
public class ContactableBean implements ContactableLocal {
    @EJB
    private NodeLocal nodeBean;

    @PersistenceContext
    private EntityManager em;

    @EJB
    private SearchLocal m_searchLocal;

    public Set<Person> getNestedPersonSet(KeyLocal key, Contactable contactable) {
        key.validateKey();
        Set<Person> personSet = new HashSet<Person>();
        recursivePersonAdd(personSet, contactable);
        return personSet;
    }

    private void recursivePersonAdd(Set<Person> personSet, Contactable contactable) {
        if (contactable instanceof Person) {
            personSet.add((Person)contactable);
        }
        for (Node node: (List<Node>) em.createNamedQuery("findOrderedChildren").setParameter("node", contactable).getResultList()) {
            if (node instanceof Contactable) {
                recursivePersonAdd(personSet, (Contactable)node);
            }
        }
    }

    @Override
    public TreeModel getContactableTree(KeyLocal key) {
        key.validateKey();
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        Node rootNode = (Node) em.createNamedQuery("findSystemNodeByName").
                setParameter("name", "Setup Organisations").
                getSingleResult();
        addContactableBranch(key, root, rootNode, false);
        return new DefaultTreeModel(root);
    }

    private void addContactableBranch(KeyLocal key, DefaultMutableTreeNode parent, Node node, boolean permission) {
        // first add each child if it is contactable
        em.refresh(node);
        for (Node child: (List<Node>) em.createNamedQuery("findOrderedChildren").setParameter("node", node).getResultList()) {
            if (child instanceof Contactable) {
                Contactable contactable = (Contactable)child;
                // make sure that the contactable hasn't been deleted!
                if (contactable.isDeleted() == false) {
                    // if the user has permission to view the contactable then add it
                    if (permission || m_searchLocal.getAssignedPermissions(key, contactable).contains(Permission.NODE_VIEW)) {
                        DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(contactable);
                        parent.add(treeNode);
                        addContactableBranch(key, treeNode, child, true);
                    }
                    // but check the branches even if not permission to see this
                    else {
                        addContactableBranch(key, parent, child, false);
                    }
                }
            }
        }
    }

    public Set<Contactable> getNestedContactebles(KeyLocal key, Contactable contactable) {
        key.validateKey();
        Set<Contactable> contactableSet = new HashSet<Contactable>();
        recursiveContactableAdd(contactableSet, contactable);
        return contactableSet;
    }


    private void recursiveContactableAdd(Set<Contactable> contactables, Contactable contactable) {
        for (Node node: (List<Node>) em.createNamedQuery("findOrderedChildren").setParameter("node", contactable).getResultList()) {
            if (node instanceof Contactable) {
                Contactable childContactable = (Contactable)node;
                contactables.add(childContactable);
                recursiveContactableAdd(contactables, childContactable);
            }
        }
    }

    public DirectoryDTO getDirectoryDTO(KeyLocal key) {
        key.validateKey();
        Map<Long,Integer> typeMap = new HashMap<Long,Integer>();
        // get the contact detail type values that appear in the directory
        List<ContactDetailType> contactDetailTypes = em.createNamedQuery("findContactDetailTypeInDirectory").getResultList();
        int index = 0;
        for (ContactDetailType contactDetailType: contactDetailTypes) {
            typeMap.put(contactDetailType.getId(), index);
            index++;
        }
        DirectoryDTO directoryDTO = new DirectoryDTO(contactDetailTypes);
        TreeModel treeModel = getContactableTree(key);
        recursiveAddContactable(treeModel, treeModel.getRoot(), typeMap, "", directoryDTO);
        return directoryDTO;
    }

    private void recursiveAddContactable(TreeModel contactableTreeModel, Object node, Map<Long,Integer> typeMap, String path, DirectoryDTO directoryDTO) {
        Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
        if (userObject instanceof Contactable) {
            Contactable contactable = (Contactable)userObject;
            long nodeId = contactable.getId();
            String name = contactable.toString();
            DirectoryDTO.Type type;
            if (contactable instanceof Organisation)
                type = DirectoryDTO.Type.ORGANISATION;
            else if (contactable instanceof Department)
                type = DirectoryDTO.Type.DEPARTMENT;
            else
                type = DirectoryDTO.Type.PERSON;
            String[] strings = new String[typeMap.size()];
            Collection<ContactDetail> contactDetails = contactable.getContactDetails();
            for (ContactDetail contactDetail: contactDetails) {
                long id = contactDetail.getContactDetailType().getId();
                if (typeMap.containsKey(id)) {
                    int index = typeMap.get(id);
                    strings[index] = contactDetail.getValue();
                }
            }
            directoryDTO.addRow(nodeId, type, path, name, strings);
            path += "/" + name;
        }
        for (int i = 0; i < contactableTreeModel.getChildCount(node); i++) {
            Object nextNode = contactableTreeModel.getChild(node, i);
            recursiveAddContactable(contactableTreeModel, nextNode, typeMap, path, directoryDTO);
        }
    }

    public void logCreate(final Contactable contactable, ChangeDelta changeDelta) {
        nodeBean.logCreate(contactable, changeDelta);
        Collection<ContactDetail> contactDetails = new ArrayList<ContactDetail>();
        for (ContactDetail contactDetail: contactable.getContactDetails()) {
            if (contactDetail.getValue().length() > 0) {
                contactDetails.add(contactDetail);
                changeDelta.addItem(ChangeDeltaItemType.ADD_CONTACT_DETAIL, contactDetail.toString());
            }
        }
        contactable.setContactDetails(contactDetails);
    }

    public void logEdit(final Contactable contactable, final Contactable current, ChangeDelta changeDelta) {
        nodeBean.logEdit(contactable, current, changeDelta);
        Map<ContactDetailType,ContactDetail> currentContactDetailMap = new HashMap<ContactDetailType,ContactDetail>();
        for (ContactDetail contactDetail: current.getContactDetails()) {
            currentContactDetailMap.put(contactDetail.getContactDetailType(), contactDetail);
        }
        List<ContactDetail> toRemove = new ArrayList<ContactDetail>();
        for (ContactDetail contactDetail: contactable.getContactDetails()) {
            if (contactDetail.getId() != null) {
                ContactDetail currentDetail = currentContactDetailMap.get(contactDetail.getContactDetailType());
                if (contactDetail.getValue().equals("")) {
                    toRemove.add(currentDetail);
                    changeDelta.addItem(ChangeDeltaItemType.REMOVE_CONTACT_DETAIL, currentDetail.toString());
                }
                else  {
                    if (!contactDetail.getValue().equals(currentDetail.getValue())) {
                        changeDelta.addItem(ChangeDeltaItemType.UPDATE_CONTACT_DETAIL, contactDetail.toString());
                    }
                }
            } else {
                if (!contactDetail.getValue().equals("")) {
                    changeDelta.addItem(ChangeDeltaItemType.ADD_CONTACT_DETAIL, contactDetail.toString());
                } else {
                    toRemove.add(contactDetail);
                }
            }
        }
        for (ContactDetail cd: toRemove) {
            contactable.getContactDetails().remove(cd);
            if (cd.getId() != null) {
                em.remove(cd);
            }
        }
    }

    public Contactable findContactable(KeyLocal key, Long contactableId) {
        Contactable contactable = em.find(Contactable.class, contactableId);
        key.assertHasPermission(contactable, Permission.NODE_VIEW);
        return contactable;
    }

}
